package fr.utbm.tx52.reception;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log;
import android.util.Pair;
import android.widget.Toast;

import java.io.*;
import java.net.*;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import fr.utbm.tx52.R;
import fr.utbm.tx52.contactsManagement.Contact;
import fr.utbm.tx52.contactsManagement.ContactList;
import fr.utbm.tx52.dataManagement.DataManager;

/**
 * Service qui va surveiller la reception de message en mode pull 
 * 
 * C'est ce service qui a la socket de communication et qui envoi et reçoit les messages du serveur
 *
 */

public class PullService extends Service {

	/**
	 * Port utilisé sur le serveur
	 */
	private static final int port = 8080;
	
	/**
	 * Adresse du serveur
	 */
	private static final String IPServer = "192.168.0.1";

	/**
	 * Taille du buffer pour l'envoi de fichiers
	 */
	//private static final int bufferSize = 4096;
	
	/**
	 * Identifiant notification logiciel
	 */
	private static final int ID_NOTIFICATION = 1989;

	
	/**
	 * Instance du service
	 */
	private static PullService m_instance = null;
	
	/**
	 * La socket de communcation avec le serveur
	 */
	Socket m_socket;
	
	/**
	 * Flux de sortie
	 */
	private ObjectOutputStream m_out;
	
	/**
	 * Flux d'entrée
	 */
	private ObjectInputStream m_in;
	
	
	
	/**
	 * Obtient l'instance du service
	 */
	public static PullService getInstance()
	{
		return m_instance;
	}
	
	
	@Override
	public void onCreate() {
		Log.d(this.getClass().getName(), "Création du service");
		super.onCreate();	
		m_socket = null;
		m_instance = this;
	}
	
	 
	/**
	 * Le corp du service
	 */
	@Override 
	public int onStartCommand(Intent intent, int flags, int startId) { 
		Log.d(this.getClass().getName(), "Service démarré"); 
		
		final Context context = this;
		
		// passe en thread car readline est bloquant
		Thread t = new Thread(new Runnable() {

			public void run() {
				
				while (true) {
				
					Log.d(this.getClass().getName(), "Création de la socket...");
					try {
						m_socket = new Socket(IPServer, port);
						m_out = new ObjectOutputStream(m_socket.getOutputStream());
						m_in = new ObjectInputStream(m_socket.getInputStream());
						Log.d(this.getClass().getName(), "La socket a bien été créée !");
					} catch (Exception e) {
						Log.d(this.getClass().getName(), "Erreur lors de la création de la socket : " + e.getMessage());
						m_socket = null;
					}
					
					sendIdentification();
					
					Object message;
					try {
						while (true) {
							
							message = m_in.readObject();
							
							if ( message == null) {
								continue;
							}
							
							Log.d(this.getClass().getName(), "Message recu : '" + message.toString() + "'");
							
							if ( message instanceof String ) {
							
								String strMessage = (String)message;
								
								if ( strMessage.startsWith("+")) { // demande d'ajout d'un ami
									
									Log.d(this.getClass().getName(), "Demande d'ajout d'un ami");
									String phone = strMessage.split(" ")[1];
									ContactList.addContactInWaitingList(strMessage.split(" ")[1]);
									createNotification("Demande d'ajout d'ami", Contact.getContactsByPhone(context, phone).getName() + " souhaite pouvoir vous envoyer des photos.", ContactList.class);
								}
								
								else if (strMessage.startsWith("@")) {	// on doit recevoir une photo avec commentaire et tout
									
									// param1 = from
									// param2 = nom du fichier image
									// param3 = nom du fichier son associé ou null si aucun
									// reste = commentaire écrit
									
									String[] args = strMessage.split(" ");
									
									String comment = "";
									for (int i=4; i<args.length; ++i) {
										comment += args[i] + " ";
									}
									
									// enregistrement de l'image
									FileOutputStream fosImg = new FileOutputStream(DataManager.DATA_PATH + args[2]);
									byte[] buffer = (byte[])m_in.readObject();
									fosImg.write(buffer);
									fosImg.flush();
									fosImg.close();
									
									// enregistrement du commentaire audio
									if ( ! args[3].equals("null") ) {
										FileOutputStream fosSnd = new FileOutputStream(DataManager.DATA_PATH + args[3]);
										byte[] bufferSnd = (byte[])m_in.readObject();
										fosSnd.write(bufferSnd);
										fosSnd.flush();
										fosSnd.close();
									}
									
									Log.d(getClass().getName(), "===============================================");
									Log.d(getClass().getName(), "Envoyé par : " + args[1]);
									Log.d(getClass().getName(), "Nom du fichier image : " + args[2]);
									Log.d(getClass().getName(), "Nom du fichier son : " + args[3] + (args[3].equals("null") ? " (aucun)" : ""));
									Log.d(getClass().getName(), "Commentaire écrit : " + comment);
									Log.d(getClass().getName(), "===============================================");
									
									List<Pair<String, String>> params = new ArrayList<Pair<String,String>>();
									params.add(new Pair<String, String>("comment", comment));
									params.add(new Pair<String, String>("sendby", args[1]));
									params.add(new Pair<String, String>("photopath", args[2]));
									params.add(new Pair<String, String>("soundpath", args[3]));
									
									createNotification("Nouvelle photo", Contact.getContactsByPhone(context, args[1]).getName() + " a partagé une photo avec vous !", ViewPhoto.class, params);
								}
							}
							
						}
					} catch (Exception e) {
						Log.d(this.getClass().getName(), "Erreur pendant l'execution du service : " + e.getMessage()); 
					} finally {
						m_socket = null;
					}
			
					// il y a eu une erreur, on va donc attendre un peu avant de tenter une reconnexion
					try {
						Thread.sleep(5000);
					} catch (InterruptedException e) {
					}
				}
			}
		});

		t.start();
		
		return START_NOT_STICKY; 
	} 

	
	/**
	 * Envoi l'identification au serveur, c'est à dire le numéro de téléphone
	 */
	public static synchronized void sendIdentification()
	{
		PullService ps = getInstance();
		
		if ( ps.m_socket == null ) {
			//Toast.makeText(ps.getApplicationContext(), "Impossible de se connecter au serveur", Toast.LENGTH_LONG).show();
			return;
		}	
		
		SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ps.getApplicationContext());
		String number = prefs.getString("phonenumber", "");
		Log.d(ps.getClass().getName(), "phone number : " + number);
		if ( number != "" ) {
			ps.sendMessage(MessageType.IDENTIFICATION, number);
		}
	}
	
	
	
	/**
	 * Envoi un message au serveur
	 * 
	 * @param type
	 * 				Le type du message, par exemple # pour s'identifier
	 * @param message
	 * 				Le message a passer en argument
	 */
	public void sendMessage(MessageType type, String message) {
		
		if ( m_socket == null ) {
			Toast.makeText(getApplicationContext(), "Impossible de se connecter au serveur, le message ne sera donc pas envoyé", Toast.LENGTH_LONG).show();
			return;
		}	
		
		synchronized (m_out) { 
			Log.d(getClass().getName(), "Sending message : " + type.getSymbol() + " " + message);
			try {
				m_out.writeObject(new String(type.getSymbol() + " " + message));
				m_out.flush();
			} catch (IOException e) {
				Log.d(getClass().getName(), "Erreur lors de l'envoi du message : " + type.getSymbol() + " " + message + " -> " + e.getMessage());
			}
		}
	}
	
	
	/**
	 * Envoi un message au serveur
	 * 
	 * @param type
	 * 				Le type du message, par exemple # pour s'identifier
	 * @param args
	 * 				Les arguments a envoyer, exemple le numéro de téléphone si on a envoyé #
	 */
	public void sendMessage(MessageType type, List<String> args) {
		
		if ( m_socket == null ) {
			Toast.makeText(getApplicationContext(), "Impossible de se connecter au serveur, le message ne sera donc pas envoyé", Toast.LENGTH_LONG).show();
			return;
		}
		
		String message = type.getSymbol() + " ";
		for (Iterator<String> it = args.iterator(); it.hasNext(); ) {
			message += it.next() + " ";
		}
		synchronized (m_out) { 
			Log.d(getClass().getName(), "Sending message : " + message);
			try {
				m_out.writeObject(message);
				m_out.flush();
			} catch (IOException e) {
				Log.d(getClass().getName(), "Erreur lors de l'envoi du message : " + message + " -> " + e.getMessage());
			}
		}
	}
	
	
	/**
	 * Envoi d'un fichier dans la socket
	 * 
	 * @param path
	 * 				Le chemin du fichier
	 * 
	 * @param message
	 * 				Le message qui sera affiché sous la barre de progression
	 * 
	 * @param type
	 * 				Le type du message (FILE_SOUND ou FILE_IMAGE)
	 */
	public void sendFile(String path, MessageType type)
	{
		if ( m_socket == null ) {
			Toast.makeText(getApplicationContext(), "Impossible de se connecter au serveur, le message ne sera donc pas envoyé", Toast.LENGTH_LONG);
			return;
		}
		
		synchronized (m_out) {
			
			File f = new File(path); 
			
			if( ! f.exists()) {
				Log.e(getClass().getName(), "Tentative d'envoi d'un fichier inexistant : " + path);
				return;
			}
			
			try {

				// on annonce qu'on va envoyer un son
				m_out.writeObject(new String(type.getSymbol()));
				m_out.flush();
				
				Log.d(this.getClass().getName(), "Envoi du fichier " + f.toURI().toURL());
				
				long fileSize = f.length(); 
				Log.d(this.getClass().getName(), "Taille du fichier : " + fileSize); 
				/*
				long loopCount = fileSize / bufferSize; 
				Log.d(this.getClass().getName(), "Nombre de packets : " + loopCount); 
				*/
				
				FileInputStream fis = new FileInputStream(path);

				//
				byte[] message = new byte[(int) f.length()];
				fis.read(message);
				m_out.writeObject(message);
				m_out.flush();
				//
				
				/*
				byte[] buffer = new byte[bufferSize];
				int read;
				while ( (read = fis.read(buffer)) != -1 ) {
					if ( read != bufferSize ) {
						break;
					}
					m_out.writeObject(buffer);
					m_out.flush();
				}
				
				if ( read != bufferSize && read != -1 ) {
					byte[] lastBuffer = new byte[read];
					for (int i=0; i<read; ++i) {
						lastBuffer[i] = buffer[i];
					}
					m_out.writeObject(lastBuffer);
					m_out.flush();
				}
				*/
			} catch (Exception e) {
				Log.d(this.getClass().getName(), "Erreur lors de l'envoi du fichier : " + e.getMessage());
			}			
		}
		
	}
	
	
	  @Override
	  public void onDestroy() {
		  Log.d(this.getClass().getName(), "Le service est terminé !"); 
		  try {
			  m_socket.close();
		  } catch (IOException e) {
		  }
	  }


	@Override
	public IBinder onBind(Intent arg0) {
		// TODO Auto-generated method stub
		return null;
	}
	
	
    /**
     * Création d'une notification
     * 
     * @param title
     * 				Le titre de la notification
     * @param content
     * 				Le contenu de la notification
     * @param target
     * 				L'activité à ouvrir quand on clique sur la notification
     * @param params
     * 				Les paramètres a passer à l'activité <clef, valeur>
     */
    private void createNotification(String title, String content, Class<?> target, List<Pair<String, String>> params){
    	
    	cancelNotification(getApplicationContext());
    	
    	//On créer un "gestionnaire de notification"
    	NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);        
 
    	//On créer la notification
    	//Avec son icône et son texte défilant (optionel si l'on veut pas de texte défilant on met cet argument à null)
    	Notification notification = new Notification(R.drawable.icon, title, System.currentTimeMillis());
 
    	//Le PendingIntent c'est ce qui va nous permettre d'atteindre notre deuxième Activity
    	//ActivityNotification sera donc le nom de notre seconde Activity
    	Intent i = new Intent(this, target);
    	
    	for (Pair<String, String> p : params) {
    		i.putExtra(p.first, p.second);
    	}
    	
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, i, Intent.FLAG_ACTIVITY_NEW_TASK);
        
        //On configure notre notification avec tous les paramètres que l'on vient de créer
        notification.setLatestEventInfo(this, title, content, pendingIntent);
        //On ajoute un style de vibration à notre notification
        //L'utilisateur est donc également averti par les vibrations de son téléphone
        //Ici les chiffres correspondent à 0sec de pause, 0.2sec de vibration, 0.1sec de pause, 0.2sec de vibration, 0.1sec de pause, 0.2sec de vibration
        //Vous pouvez bien entendu modifier ces valeurs à votre convenance
        //notification.vibrate = new long[] {0,200,100,200,100,200};
 
        //Enfin on ajoute notre notification et son ID à notre gestionnaire de notification
        notificationManager.notify(ID_NOTIFICATION, notification);
    
    }
	
    
    /**
     * Création d'une notification
     * 
     * @param title
     * 				Le titre de la notification
     * @param content
     * 				Le contenu de la notification
     * @param target
     * 				L'activité à ouvrir quand on clique sur la notification
     */
    private void createNotification(String title, String content, Class<?> target){
    	
    	cancelNotification(getApplicationContext());
    	
    	//On créer un "gestionnaire de notification"
    	NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);        
 
    	//On créer la notification
    	//Avec son icône et son texte défilant (optionel si l'on veut pas de texte défilant on met cet argument à null)
    	Notification notification = new Notification(R.drawable.icon, title, System.currentTimeMillis());
 
    	//Le PendingIntent c'est ce qui va nous permettre d'atteindre notre deuxième Activity
    	//ActivityNotification sera donc le nom de notre seconde Activity
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, target), 0);
        
        //On configure notre notification avec tous les paramètres que l'on vient de créer
        notification.setLatestEventInfo(this, title, content, pendingIntent);
        //On ajoute un style de vibration à notre notification
        //L'utilisateur est donc également averti par les vibrations de son téléphone
        //Ici les chiffres correspondent à 0sec de pause, 0.2sec de vibration, 0.1sec de pause, 0.2sec de vibration, 0.1sec de pause, 0.2sec de vibration
        //Vous pouvez bien entendu modifier ces valeurs à votre convenance
        //notification.vibrate = new long[] {0,200,100,200,100,200};
 
        //Enfin on ajoute notre notification et son ID à notre gestionnaire de notification
        notificationManager.notify(ID_NOTIFICATION, notification);
    }
    
    /**
     * Supprime la notification créée par le logiciel
     * 
     * @param c
     * 			Le context de l'application
     */
    public static void cancelNotification(Context c){
    	//On créé notre gestionnaire de notfication
    	NotificationManager notificationManager = (NotificationManager)c.getSystemService(Context.NOTIFICATION_SERVICE);
    	//on supprime la notification grâce à son ID
    	notificationManager.cancel(ID_NOTIFICATION);
    }
	
}
